/**
 *
 */
package edu.tjut.tjutcommunity.recommend.contentbasedrecommend;


import edu.tjut.tjutcommunity.entity.Post;
import edu.tjut.tjutcommunity.entity.Postlogs;
import edu.tjut.tjutcommunity.entity.User;
import edu.tjut.tjutcommunity.mapper.PostMapper;
import edu.tjut.tjutcommunity.recommend.algorithms.JsonKit;
import edu.tjut.tjutcommunity.recommend.algorithms.RecommendKit;
import edu.tjut.tjutcommunity.service.PostlogsService;
import edu.tjut.tjutcommunity.service.UserService;
import org.ansj.app.keyword.Keyword;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @author tjut
 * @email
 * @github
 * @date 每次用户浏览新的新闻时，用以更新用户的喜好关键词列表
 */
@Component
public class UserPrefRefresher {
    //设置TFIDF提取的关键词数目
    private static final int KEY_WORDS_NUM = 10;

    //每日衰减系数
    private static final double DEC_COEE = 0.7;
    final
    UserService userService;
    final
    PostlogsService postlogsService;
    final
    PostMapper postMapper;

    public UserPrefRefresher(UserService userService, PostlogsService postlogsService, PostMapper postMapper) {

        this.userService = userService;
        this.postlogsService = postlogsService;
        this.postMapper = postMapper;
    }

    public void refresh() {
        refresh(RecommendKit.getUserList());
    }

    /**
     * 按照推荐频率调用的方法，一般为一天执行一次。
     * 定期根据前一天所有用户的浏览记录，在对用户进行喜好关键词列表TFIDF值衰减的后，将用户前一天的关键词及相应TFIDF值更新到列表中去。
     *
     * @param userIdsCol
     */
    @SuppressWarnings("unchecked")
    public void refresh(Collection<Integer> userIdsCol) {
        //首先对用户的喜好关键词列表进行衰减更新
        autoDecRefresh(userIdsCol);


        HashMap<Integer, ArrayList<Integer>> userBrowsedMap = getBrowsedHistoryMap();
        if (userBrowsedMap.size() == 0)
            return;

        //用户喜好关键词列表：userPrefListMap:<String(userid),String(json))>
        HashMap<Integer, CustomizedHashMap<Integer, CustomizedHashMap<String, Double>>> userPrefListMap = RecommendKit.getUserPrefListMap(userBrowsedMap.keySet());

        HashMap<String, Object> postsTFIDFMap = getPostsTFIDFMap();

        //开始遍历用户浏览记录，更新用户喜好关键词列表
        //对每个用户（外层循环），循环他所看过的每条帖子（内层循环），对每个帖子，更新它的关键词列表到用户的对应模块中

        for (Integer userId : userBrowsedMap.keySet()) {
            //获取用户看过的帖子
            ArrayList<Integer> postsList = userBrowsedMap.get(userId);
            for (Integer post : postsList) {
                //获取typeid
                Integer moduleId = (Integer) postsTFIDFMap.get(post + "typeid");
                //获得对应模块的（关键词：喜好）map
                CustomizedHashMap<String, Double> rateMap = userPrefListMap.get(userId).get(moduleId);
                //获得帖子的（关键词：TFIDF值）map
                List<Keyword> keywordList = (List<Keyword>) postsTFIDFMap.get(post.toString());
                for (Keyword keyword : keywordList) {
                    String name = keyword.getName();
                    if (rateMap.containsKey(name)) {
                        rateMap.put(name, rateMap.get(name) + keyword.getScore());
                    } else {
                        rateMap.put(name, keyword.getScore());
                    }
                }
                userPrefListMap.get(userId);
            }
        }
        for (Integer userId : userBrowsedMap.keySet()) {
            try {

                //更新
                User user = new User();
                user.setUid(userId);
                user.setPrefList(userPrefListMap.get(userId).toString());
                userService.updateUserById(user);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 所有用户的喜好关键词列表TFIDF值随时间进行自动衰减更新
     */
    public void autoDecRefresh() {
        autoDecRefresh(RecommendKit.getUserList());
    }

    /**
     * 所有用户的喜好关键词列表TFIDF值随时间进行自动衰减更新
     */
    public void autoDecRefresh(Collection<Integer> userIdsCol) {
        try {
            if (userIdsCol.isEmpty()) return;
            List<User> userList = userService.getUsers(userIdsCol);
            //用以更新的用户喜好关键词map的json串
            //用于删除喜好值过低的关键词
            ArrayList<String> keywordToDelete = new ArrayList<String>();
            for (User user : userList) {
                String newPrefList = "{";
                //key为类别，value为map，map的键为关键字，值为分数
                String prefList = user.getPrefList();
                //最外层是模块id，对应的是用户在该模块的喜好列表
                HashMap<Integer, CustomizedHashMap<String, Double>> map = JsonKit.jsonPrefList2Map(prefList);

                //遍历键
                for (Integer moduleId : map.keySet()) {
                    //用户对应模块的喜好不为空,模块id
                    CustomizedHashMap<String, Double> moduleMap = map.get(moduleId);
                    newPrefList += "\"" + moduleId + "\":";
                    if (!(moduleMap.toString().equals("{}"))) {
                        for (String key : moduleMap.keySet()) {
                            //累计TFIDF值乘以衰减系数
                            double result = moduleMap.get(key) * DEC_COEE;
                            if (result < 10) {
                                keywordToDelete.add(key);
                            }
                            moduleMap.put(key, result);
                        }
                    }
                    for (String deleteKey : keywordToDelete) {
                        moduleMap.remove(deleteKey);
                    }
                    keywordToDelete.clear();
                    newPrefList += moduleMap.toString() + ",";
                }
                newPrefList = "'" + newPrefList.substring(0, newPrefList.length() - 1) + "}'";
                user.setPrefList(newPrefList);
                userService.updateUserById(user);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 提取出当天有浏览行为的用户及其各自所浏览过的帖子id列表
     *
     * @return
     */
    private HashMap<Integer, ArrayList<Integer>> getBrowsedHistoryMap() {
        HashMap<Integer, ArrayList<Integer>> userBrowsedMap = new HashMap<Integer, ArrayList<Integer>>();
        try {
            String specificDayFormat = RecommendKit.getSpecificDayFormat(-3);
            List<Postlogs> postlogsList = postlogsService.getPostlogsHistory(specificDayFormat);
            for (Postlogs postlogs : postlogsList) {
                //如果该记录的用户已在记录中，加入即可
                if (userBrowsedMap.containsKey(postlogs.getUid())) {
                    userBrowsedMap.get(postlogs.getUid()).add(postlogs.getPid());
                } else {
                    //假如没有，新建
                    userBrowsedMap.put(postlogs.getUid(), new ArrayList<Integer>());
                    userBrowsedMap.get(postlogs.getUid()).add(postlogs.getPid());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return userBrowsedMap;
    }


    /**
     * 获得浏览过的帖子的集合
     *
     * @return
     */
    private HashSet<Integer> getBrowsedNewsSet() {
        HashMap<Integer, ArrayList<Integer>> browsedMap = getBrowsedHistoryMap();
        HashSet<Integer> newsIdSet = new HashSet<Integer>();
        for (Integer integer : getBrowsedHistoryMap().keySet()) {
            newsIdSet.addAll(browsedMap.get(integer));
        }
        return newsIdSet;
    }

    /**
     * 将所有当天被浏览过的帖子提取出来，以便进行TFIDF求值操作，以及对用户喜好关键词列表的更新。
     *
     * @return
     */
    private HashMap<String, Object> getPostsTFIDFMap() {
        HashMap<String, Object> postTFIDFMap = new HashMap<String, Object>();
        try {
            HashSet<Integer> postIds = getBrowsedNewsSet();

            //提取出所有新闻的关键词列表及对应TF-IDf值，并放入一个map中
            List<Post> postList = getPostByIds(postIds);
            for (Post post : postList) {
//                关键词
                postTFIDFMap.put(String.valueOf(post.getPid()), TFIDF.getTFIDF(post.getTitle(), post.getDescription(), KEY_WORDS_NUM));
//                帖子和type对应
                postTFIDFMap.put(post.getPid() + "typeid", post.getType());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return postTFIDFMap;
    }

    public List<Post> getPostByIds(Set<Integer> pids) {
        if (pids.isEmpty()) return Collections.EMPTY_LIST;
        return postMapper.selectBatchIds(pids);
    }
}
